PURRR
PURRR es una librería en R que contiene funciones y métodos de programación funcional dentro del entorno de tidyverse.
MAP
MAP es una función para aplicar una función a cada elemento de una estructura de datos como una lista o vector. El funcionamiento de map consiste en recorrer (“loopear”) la estructura aplicando la función a cada elemento y guardando los resultados. De esta manera podemos reemplazar los loops por una forma más legible y clara de código.
La función map toma como argumentos:
map(VECTOR_O_LIST_INPUT, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Veamos un ejemplo con un vector numérico
# Aplicamos la función log10 sobre un vector
map(.x = c(10,100,1000), .f=log10)
[[1]]
[1] 1
[[2]]
[1] 2
[[3]]
[1] 3
Map: tipos específicos
Veamos que el resultado de map es una lista. Si sabemos cuál es el tipo de dato de nuestros resultados podemos usar variaciones de map que explicitan el tipo de dato y devuelven los resultados en un vector. Algunas de ellas son:
map_lgl() devuelve un vector de booleanos
map_dbl() devuelve un vector numérico
map_chr() devuelve un vector de strings
Volvamos a ver nuestro ejemplo usando map_dbl
map_dbl(.x = c(10,100,1000), .f=log10)
[1] 1 2 3
Map: funciones
Se pueden aplicar funciones de R base o de las librerías como así también funciones definidas por el usuario. Muchas veces no es necesario definir una función de antemano, sino que podemos usar funciones anónimas o funciones lambda.
Funciones anónimas
La función se define en una única linea.
Ejemplo numérico
Por ejemplo, calculamos una función cuadrática para un vector numérico que va del 1 al 10.
# Calculamos una función cuadrática para el vector
map_dbl(.x = c(1:10), .f = function(x) x^2+1)
[1] 2 5 10 17 26 37 50 65 82 101
Ejemplo con strings
A partir de un vector de strings queremos contar la cantidad de letras “A” en cada elemento. Para eso combinamos las funciones str_to_upper y str_count.
map_dbl(.x = c("papas FRITAS", "batatas HERVIDAS", "boniato AL HORNO"),
.f = function(x) str_count(string = str_to_upper(x), pattern = "A"))
[1] 3 4 2
Funciones lambda
Son una forma aún más concisa de declarar funciones, aunque pueden ser más limitadas. Las variables se declaran como .x
# Calculamos otra función cuadrática para el vector
map_dbl(.x = c(1:10), .f = ~.x^2 - .x)
[1] 0 2 6 12 20 30 42 56 72 90
Map: Múltiples argumentos
Usamos map2 cuando queremos aplicar una función sobre dos listas o vectores:
- map2(.x, .y, .f, …)
- map2(INPUT_UNO, INPUT_DOS, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Ejemplo numérico
Veamos un ejemplo para generar observaciones de distribuciones normales a partir de los vectores de mu y sigma (este es el ejemplo del libro R for Data Science).
La funcion rnorm sirve para generar muestras aleatorias de distribución normal con media igual a mu y desviación estándar igual a sigma, donde n indica la cantidad de observaciones a generar.
# Vector de mu = medias
mu = c(0, 3, -5, 2.1)
# Vector de sigma = desvíos estándar
sigma = c(1, 0.5, 5, 0.01)
# Generamos 10 observaciones a partir de nuestros vectores
map2(.x = mu, .y = sigma, .f= rnorm, n = 10)
[[1]]
[1] 0.9945465 0.8976740 1.2695762 -1.4685437 0.5199419 0.1595166
[7] -0.3307085 1.5434118 0.3516043 -1.2951256
[[2]]
[1] 3.185547 3.058735 3.495075 2.169947 2.559699 2.182306 2.722014
[8] 2.086243 2.703892 2.668012
[[3]]
[1] 7.126446 1.941306 -10.618847 -1.310431 -6.701077 -9.364052
[7] -3.503014 -10.254412 -4.606006 -13.314767
[[4]]
[1] 2.100392 2.111527 2.092098 2.113321 2.085242 2.116517 2.098858
[8] 2.076954 2.110509 2.096334
Ejemplo con strings
Ahora veamos con un ejemplo para crear una lista de compras.
# Vectores
kilos = c(10, 1, 6, 8, 3)
verduras = c("papa", "batata", "zapallo", "boniato", "zanahoria")
map2_chr(.x = verduras, .y =kilos, .f = function(x,y) str_c(str_to_title(x),y, sep=": "))
[1] "Papa: 10" "Batata: 1" "Zapallo: 6" "Boniato: 8"
[5] "Zanahoria: 3"
Usamos pmap cuando queremos aplicar una función sobre una lista de argumentos:
- pmap(.l, .f, …)
- pmap(VECTOR_O_LIST_DE_INPUTS, FUNCTION_A_APLICAR, OTROS_OPCIONALES)
Volvamos al ejemplo para generar observaciones de distribuciones normales a partir de los vectores de mu y sigma, pero ahora incorporamos la cantidad de observaciones como un argumento adicional (este ejemplo también es del libro R for Data Science).
# Vector de mu
mu = c(0, 3, -5, 2.1)
# Vector de sigma
sigma = c(1, 0.5, 5, 0.01)
# Vector de cantidad de observaciones
n = c(1:4)
# Lista de argumentos
argumentos <- list(n, mu, sigma)
# Generamos las observaciones a partir de nuestra lista de parámetros
pmap(.l = argumentos, .f = rnorm)
[[1]]
[1] 0.7342365
[[2]]
[1] 3.273093 3.725330
[[3]]
[1] -7.151900 1.036900 -3.445161
[[4]]
[1] 2.110439 2.103634 2.100693 2.098944
PURRR: Dataframes y modelos
Dataframes
Las funciones de PURRR se pueden utilizar para realizar operaciones sobre dataframes. La sintaxis que vamos a utilizar es:
map(dataframe, función)
De esta manera vamos a estar aplicando la función sobre todas las columnas del dataframe. Veamos un ejemplo usando nuestra encuesta de sueldos del sector IT limpia.
# cargamos el dataset
encuesta_sueldos_limpia = read_csv("../Fuentes/encuesta_ds_final.csv")
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
edad = col_double(),
anos_de_experiencia = col_double(),
anos_en_la_empresa_actual = col_double(),
anos_en_el_puesto_actual = col_double(),
trabajo_de = col_character(),
salario_bruto = col_double(),
salario_neto = col_double(),
sueldo_dolarizado = col_logical()
)
encuesta_sueldos_limpia %>%
# creamos variable perfil BI/DA vs DS/DI
mutate(perfil = case_when(trabajo_de == "BI Analyst / Data Analyst" ~ "BI/DA", TRUE ~ "DS/DE"))
Comencemos analizando la cantidad de valores distintos para cada columna. Para ello usamos la función n_distinct.
map(encuesta_sueldos_limpia, n_distinct)
$X1
[1] 244
$edad
[1] 31
$anos_de_experiencia
[1] 25
$anos_en_la_empresa_actual
[1] 17
$anos_en_el_puesto_actual
[1] 13
$trabajo_de
[1] 2
$salario_bruto
[1] 116
$salario_neto
[1] 115
$sueldo_dolarizado
[1] 1
Usando la función str() se puede consultar de forma compacta la estructura interna de un objeto R. Es especialmente adecuado para mostrar de forma compacta el contenido (abreviado) de listas.
map(encuesta_sueldos_limpia, n_distinct) %>%
str() # Agregamos str para que la salida luzca mejor
List of 9
$ X1 : int 244
$ edad : int 31
$ anos_de_experiencia : int 25
$ anos_en_la_empresa_actual: int 17
$ anos_en_el_puesto_actual : int 13
$ trabajo_de : int 2
$ salario_bruto : int 116
$ salario_neto : int 115
$ sueldo_dolarizado : int 1
Ahora calculamos el promedio sobre las columnas numéricas.
encuesta_sueldos_limpia %>%
select_if(is.numeric) %>%
map(mean) %>%
str()
List of 7
$ X1 : num 122
$ edad : num 30.6
$ anos_de_experiencia : num 6.01
$ anos_en_la_empresa_actual: num 2.7
$ anos_en_el_puesto_actual : num 2.15
$ salario_bruto : num 122656
$ salario_neto : num 62141
Calculamos la mediana sobre las columnas numéricas. Sabiendo que el resultado de la función es un número podemos usar directamente la función map_dbl.
# usando map_dbl
encuesta_sueldos_limpia %>%
select_if(is.numeric) %>%
map_dbl(median)
X1 edad
122.5 30.0
anos_de_experiencia anos_en_la_empresa_actual
4.0 1.0
anos_en_el_puesto_actual salario_bruto
1.0 75000.0
salario_neto
60000.0
Recordemos que esta función devuelve un vector numérico no una lista como en el caso anterior, por eso no usamos la función str() para ver el resultado en forma compacta.
Modelos
Podemos usar la función map para calcular distintos modelos modelos. Por ejemplo, calculemos un modelo distinto según el perfil (DA y DS).
modelos_por_perfil = encuesta_sueldos_limpia %>%
# separamos el dataset según perfil
split(.$perfil) %>%
# creamos un modelo para cada perfil
map(~lm(salario_neto ~ edad, data = .))
El resultado de esto es una lista con los dos modelos. Analicemos los elementos de cada uno utilizando la función tidy() de la librería broom.
modelos_por_perfil %>% map(broom::tidy)
$`BI/DA`
$`DS/DE`
NA
Evaluemos ahora la capacidad explicativa de los modelos. Para ello, comparamos el \(R^2\) de cada uno (en este caso como son modelos simples podemos utilizar esta métrica).
modelos_por_perfil %>%
map(broom::glance) %>%
map_dbl("r.squared")
BI/DA DS/DE
0.1568223 0.1293064
Mientras el modelo de edad para los perfiles BI/DA logra explicar el 15.6% de la variabilidad del fenómeno, el modelo de edad para los DS/DE logra captar un porcentaje menor de la variabilidad (12,9%).
Ejemplo: Iterando en la EPH
Lo primero que necesitamos es definir un vector o lista sobre el que iterar.
Por ejemplo, podemos armar un vector con los path a las bases individuales, con el comando fs::dir_ls. Se le especifica el path donde buscar los archivos y en este caso una expresion regular regexp para que devuelva aquellos archivos que coinciden con la misma.
# Buscamos en el path aquellos aquellos archivos que matchean a la expresion regular
bases_individuales_path <- dir_ls(path = '../Fuentes/', regexp = 'usu_individual_T')
bases_individuales_path
../Fuentes/usu_individual_T119.txt ../Fuentes/usu_individual_T120.txt
../Fuentes/usu_individual_T219.txt ../Fuentes/usu_individual_T319.txt
../Fuentes/usu_individual_T419.txt
Luego, como en la función que usamos para leer las bases definimos muchos parámetros, nos podemos armar una función wrapper que sólo necesite un parámetro y simplifique la escritura del map.
# Leer la base de EPH tomando como argumento el file_path
leer_base_eph <- function(path) {
# Lectura de archivos
read.table(path, sep = ";", dec = ",", header = TRUE, fill = TRUE) %>%
# Seleccionamos variables de interés
select(ANO4, TRIMESTRE, REGION, P21, CH04, CH06)
}
Creamos una tabla que incluya los paths de cada base en una columna y los dataframes anidados correspondientes a cada base en otra.
El resultado es un dataframe donde la columna base tiene en cada fila el dataframe con la base de la EPH de ese período. Esto es lo que llamamos un nested DF o dataframe anidado.
Si queremos abrir (“desanidar”) estos dataframes anidados usamos el comando unnest().
# Desanidamos el dataframe
bases_df <- bases_df %>% unnest()
bases_df
¿Qué pasa si los dataframes que tenemos anidados no tienen la misma cantidad de columnas?
Esto mismo lo podemos usar para fragmentar el dataset por alguna variable, con la funcion group_by()
¿ De qué sirve todo esto?
No todo en la vida es un Dataframe. Hay estucturas de datos que no se pueden normalizar a filas y columnas. En esos casos recurríamos tradicionalmente a los loops. Con MAP podemos tener los elementos agrupados en un sólo objeto y aún conservar sus formas diferentes.
Ejemplo. Regresión lineal simple
Planteamos un modelo para explicar el ingreso (P21 en nuestra base) en función de la edad (CH06 en la base).
\[
Ingreso = \beta_0 + \beta_1*Edad
\] Calculamos una regresion lineal sobre el dataset que contiene todas las bases de EPH.
(al final de la clase podemos charlar sobre los resultados)
De forma Tidy, la librería broom nos da los resultados sobre los coeficientes en un DF.
```r
```r
bases_df_lm <- bases_df %>%
# Agrupamos por region
group_by(REGION) %>%
# Anidamos
nest() %>%
# Creamos una columna que tenga el dataframe como resultado de la funcion
mutate(lm = map(data,fun))
bases_df_lm
# Desanidamos por la variable lm
bases_df_lm %>%
unnest(lm)
Si lo queremos hacer por género, veamos cómo podemos hacerlo usando MAP.
Usando MAP
Primero armamos una funcion que simplifica el código y nos devuelve la información de los coeficientes del modelo lineal calculado sobre el dataframe base.
fun <- function(base, grupo){broom::tidy(lm(P21~CH06, data = base))}
Calculamos la regresión lineal para cada grupo. En este caso elegimos género, pero podría haber sido por región también u otra variable de interés.
Obtenemos como resultado un data frame con los dos modelos anidados. Veamos los resultados del modelo, desanidando la variable lm.
# Desanidamos por la variable lm
bases_df_lm %>%
unnest(lm)
me quedé acá - falta actualizar WALK
Walk
Las funciones Walk tienen la misma forma que los map, pero se usan cuando lo que queremos iterar no genera una salida, sino que nos interesan los efectos secundarios que generan.
map2(.x = ABC_123$Letras,.y = ABC_123$Num,.f = funcion_prueba)[1:3]
Error in as_mapper(.f, ...) : objeto 'funcion_prueba' no encontrado
```r
walk2(.x = ABC_123$Letras,.y = ABC_123$Num,.f = funcion_prueba)
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
Notemos que `walk2` no devolvió un resultado
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuIyBmdW5jaW9uIHBhcmEgaW1wcmltaXJcbmltcHJpbWlyX3NhbGlkYSA8LSBmdW5jdGlvbih4LHkpe1xuICBwcmludChmdW5jaW9uX3BydWViYSh4LHkpKVxufVxuXG4jIE1hcFxubWFwMihBQkNfMTIzJExldHJhcyxBQkNfMTIzJE51bSxpbXByaW1pcl9zYWxpZGEpXG5gYGBcbmBgYCJ9 -->
```r
```r
# funcion para imprimir
imprimir_salida <- function(x,y){
print(funcion_prueba(x,y))
}
# Map
map2(ABC_123$Letras,ABC_123$Num,imprimir_salida)
<!-- rnb-source-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuIyBXYWxrXG53YWxrMihBQkNfMTIzJExldHJhcyxBQkNfMTIzJE51bSxpbXByaW1pcl9zYWxpZGEpXG5cbmBgYFxuYGBgIn0= -->
```r
```r
# Walk
walk2(ABC_123$Letras,ABC_123$Num,imprimir_salida)
```
Eso que vemos es el efecto secundario dentro de la función (imprimir)
Discusión.
¿Cuándo usar estas herramientas?
En el curso hemos visto diferentes técnicas para manipulación de datos. En particular, la librería dplyr nos permitía fácilmente modificar y crear nuevas variables, agrupando. ¿Cuando usamos dplyr y cuando usamos purrr?
Si trabajamos sobre un DF simple, sin variables anidadas (lo que conocíamos hasta hoy) podemos usar dplyr
Si queremos trabajar con DF anidados, con cosas que no son DF, o si el resultado de la operación que vamos a realizar a nivel archivo es algo distinto a un valor único, nos conviene usar map y purrr.
Las funciones walk son útiles por ejemplo para escribir archivos en disco de forma iterativa. Algo que no genera una salida.
LS0tDQp0aXRsZTogIlRpZHl2ZXJzZTogUFVSUlIiDQphdXRob3I6ICJKdWFuIEJhcnJpb2xhIHkgU29mw61hIFBlcmluaSINCmRhdGU6ICIxNyBkZSBPY3R1YnJlIGRlIDIwMjAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCkxpYnJlcmlhcyBxdWUgc2UgdXRpbGl6YW4gZW4gZXN0YSBjbGFzZToNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShvcGVueGxzeCkNCmxpYnJhcnkoZ2d0aGVtZXMpDQpsaWJyYXJ5KGZzKQ0KYGBgDQoNCkJhc2FkbyBlbiBsb3MgW3R1dG9yaWFsZXMgZGUgUFVSUlJdKGh0dHBzOi8vamVubnliYy5naXRodWIuaW8vcHVycnItdHV0b3JpYWwvaW5kZXguaHRtbCkgZGUgSmVubnkgQnJ5YW4geSBlbCBjYXDDrXR1bG8gW0l0ZXJhdGlvbl0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9pdGVyYXRpb24uaHRtbCkgZGVsIGxpYnJvIFIgZm9yIERhdGEgU2NpZW5jZSBkZSBHYXJyZXR0IEdyb2xlbXVuZCB5IEhhZGxleSBXaWNraGFtDQoNCiMgUFVSUlINCg0KW1BVUlJSXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvKSBlcyB1bmEgbGlicmVyw61hIGVuIFIgcXVlIGNvbnRpZW5lIGZ1bmNpb25lcyB5IG3DqXRvZG9zIGRlIHByb2dyYW1hY2nDs24gZnVuY2lvbmFsIGRlbnRybyBkZWwgZW50b3JubyBkZSB0aWR5dmVyc2UuDQoNCiMjIE1BUA0KDQpNQVAgZXMgdW5hICoqZnVuY2nDs24qKiBwYXJhIGFwbGljYXIgdW5hIGZ1bmNpw7NuIGEgY2FkYSBlbGVtZW50byBkZSB1bmEgZXN0cnVjdHVyYSBkZSBkYXRvcyBjb21vIHVuYSBsaXN0YSBvIHZlY3Rvci4gRWwgZnVuY2lvbmFtaWVudG8gZGUgbWFwIGNvbnNpc3RlIGVuIHJlY29ycmVyICgibG9vcGVhciIpIGxhIGVzdHJ1Y3R1cmEgYXBsaWNhbmRvIGxhIGZ1bmNpw7NuIGEgY2FkYSBlbGVtZW50byB5IGd1YXJkYW5kbyBsb3MgcmVzdWx0YWRvcy4gRGUgZXN0YSBtYW5lcmEgcG9kZW1vcyByZWVtcGxhemFyIGxvcyBsb29wcyBwb3IgdW5hIGZvcm1hIG3DoXMgbGVnaWJsZSB5IGNsYXJhIGRlIGPDs2RpZ28uDQoNCkxhIGZ1bmNpw7NuIGBtYXBgIHRvbWEgY29tbyBhcmd1bWVudG9zOg0KIA0KICogLng6IHVuIHZlY3RvciBvIGxpc3RhIGlucHV0DQogDQogKiAuZjogdW5hIGZ1bmNpw7NuIHBhcmEgYXBsaWNhcg0KIA0KbWFwKFZFQ1RPUl9PX0xJU1RfSU5QVVQsIEZVTkNUSU9OX0FfQVBMSUNBUiwgT1RST1NfT1BDSU9OQUxFUykNCg0KVmVhbW9zIHVuIGVqZW1wbG8gY29uIHVuIHZlY3RvciBudW3DqXJpY28NCg0KYGBge3J9DQojIEFwbGljYW1vcyBsYSBmdW5jacOzbiBsb2cxMCBzb2JyZSB1biB2ZWN0b3INCm1hcCgueCA9IGMoMTAsMTAwLDEwMDApLCAuZj1sb2cxMCkNCmBgYA0KDQojIyMgTWFwOiB0aXBvcyBlc3BlY8OtZmljb3MNCg0KVmVhbW9zIHF1ZSBlbCByZXN1bHRhZG8gZGUgbWFwIGVzIHVuYSBsaXN0YS4gU2kgc2FiZW1vcyBjdcOhbCBlcyBlbCB0aXBvIGRlIGRhdG8gZGUgbnVlc3Ryb3MgcmVzdWx0YWRvcyBwb2RlbW9zIHVzYXIgdmFyaWFjaW9uZXMgZGUgbWFwIHF1ZSBleHBsaWNpdGFuIGVsIHRpcG8gZGUgZGF0byB5IGRldnVlbHZlbiBsb3MgcmVzdWx0YWRvcyBlbiB1biB2ZWN0b3IuIEFsZ3VuYXMgZGUgZWxsYXMgc29uOg0KDQogICogbWFwX2xnbCgpIGRldnVlbHZlIHVuIHZlY3RvciBkZSBib29sZWFub3MNCiANCiAgKiBtYXBfZGJsKCkgZGV2dWVsdmUgdW4gdmVjdG9yIG51bcOpcmljbw0KDQogICogbWFwX2NocigpIGRldnVlbHZlIHVuIHZlY3RvciBkZSBzdHJpbmdzIA0KDQpWb2x2YW1vcyBhIHZlciBudWVzdHJvIGVqZW1wbG8gdXNhbmRvIGBtYXBfZGJsYA0KDQpgYGB7cn0NCm1hcF9kYmwoLnggPSBjKDEwLDEwMCwxMDAwKSwgLmY9bG9nMTApDQpgYGANCg0KIyMjIE1hcDogZnVuY2lvbmVzDQoNClNlIHB1ZWRlbiBhcGxpY2FyIGZ1bmNpb25lcyBkZSBSIGJhc2UgbyBkZSBsYXMgbGlicmVyw61hcyBjb21vIGFzw60gdGFtYmnDqW4gZnVuY2lvbmVzIGRlZmluaWRhcyBwb3IgZWwgdXN1YXJpby4gTXVjaGFzIHZlY2VzIG5vIGVzIG5lY2VzYXJpbyBkZWZpbmlyIHVuYSBmdW5jacOzbiBkZSBhbnRlbWFubywgc2lubyBxdWUgcG9kZW1vcyB1c2FyICoqZnVuY2lvbmVzIGFuw7NuaW1hcyoqIG8gKipmdW5jaW9uZXMgbGFtYmRhKiouDQoNCiMjIyMgRnVuY2lvbmVzIGFuw7NuaW1hcw0KDQpMYSBmdW5jacOzbiBzZSBkZWZpbmUgZW4gdW5hIMO6bmljYSBsaW5lYS4gDQoNCioqRWplbXBsbyBudW3DqXJpY28qKg0KDQpQb3IgZWplbXBsbywgY2FsY3VsYW1vcyB1bmEgZnVuY2nDs24gY3VhZHLDoXRpY2EgcGFyYSB1biB2ZWN0b3IgbnVtw6lyaWNvIHF1ZSB2YSBkZWwgMSBhbCAxMC4NCg0KYGBge3J9DQojIENhbGN1bGFtb3MgdW5hIGZ1bmNpw7NuIGN1YWRyw6F0aWNhIHBhcmEgZWwgdmVjdG9yDQptYXBfZGJsKC54ID0gYygxOjEwKSwgLmYgPSBmdW5jdGlvbih4KSB4XjIrMSkNCmBgYA0KDQoqKkVqZW1wbG8gY29uIHN0cmluZ3MqKg0KDQpBIHBhcnRpciBkZSB1biB2ZWN0b3IgZGUgc3RyaW5ncyBxdWVyZW1vcyBjb250YXIgbGEgY2FudGlkYWQgZGUgbGV0cmFzICJBIiBlbiBjYWRhIGVsZW1lbnRvLiBQYXJhIGVzbyBjb21iaW5hbW9zIGxhcyBmdW5jaW9uZXMgYHN0cl90b191cHBlcmAgeSBgc3RyX2NvdW50YC4NCg0KYGBge3J9DQptYXBfZGJsKC54ID0gYygicGFwYXMgRlJJVEFTIiwgImJhdGF0YXMgSEVSVklEQVMiLCAiYm9uaWF0byBBTCBIT1JOTyIpLA0KICAgICAgICAjIGNvbnZpZXJ0aW1vcyB0b2RhcyBhIG1hecO6c2N1bGFzIHkgY29udGFtb3MgbGEgY2FudGlkYWQgZGUgIkEiDQogICAgICAgIC5mID0gZnVuY3Rpb24oeCkgc3RyX2NvdW50KHN0cmluZyA9IHN0cl90b191cHBlcih4KSwgcGF0dGVybiA9ICJBIikpDQpgYGANCg0KIyMjIyBGdW5jaW9uZXMgbGFtYmRhDQoNClNvbiB1bmEgZm9ybWEgYcO6biBtw6FzIGNvbmNpc2EgZGUgZGVjbGFyYXIgZnVuY2lvbmVzLCBhdW5xdWUgcHVlZGVuIHNlciBtw6FzIGxpbWl0YWRhcy4gTGFzIHZhcmlhYmxlcyBzZSBkZWNsYXJhbiBjb21vIC54DQoNCmBgYHtyfQ0KIyBDYWxjdWxhbW9zIG90cmEgZnVuY2nDs24gY3VhZHLDoXRpY2EgcGFyYSBlbCB2ZWN0b3INCm1hcF9kYmwoLnggPSBjKDE6MTApLCAuZiA9ICB+LnheMiAtIC54KQ0KYGBgDQoNCiMjIyBNYXA6IE3Dumx0aXBsZXMgYXJndW1lbnRvcw0KIA0KVXNhbW9zIGBtYXAyYCBjdWFuZG8gcXVlcmVtb3MgYXBsaWNhciB1bmEgZnVuY2nDs24gc29icmUgZG9zIGxpc3RhcyBvIHZlY3RvcmVzOg0KDQotIG1hcDIoLngsIC55LCAuZiwgLi4uKQ0KLSBtYXAyKElOUFVUX1VOTywgSU5QVVRfRE9TLCBGVU5DVElPTl9BX0FQTElDQVIsIE9UUk9TX09QQ0lPTkFMRVMpDQoNCioqRWplbXBsbyBudW3DqXJpY28qKg0KDQpWZWFtb3MgdW4gZWplbXBsbyBwYXJhIGdlbmVyYXIgb2JzZXJ2YWNpb25lcyBkZSBkaXN0cmlidWNpb25lcyBub3JtYWxlcyBhIHBhcnRpciBkZSBsb3MgdmVjdG9yZXMgZGUgbXUgeSBzaWdtYSAoZXN0ZSBlcyBlbCBlamVtcGxvIGRlbCBsaWJybyAqUiBmb3IgRGF0YSBTY2llbmNlKikuDQoNCkxhIGZ1bmNpb24gYHJub3JtYCBzaXJ2ZSBwYXJhIGdlbmVyYXIgbXVlc3RyYXMgYWxlYXRvcmlhcyBkZSBkaXN0cmlidWNpw7NuIG5vcm1hbCBjb24gbWVkaWEgaWd1YWwgYSBtdSB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhciBpZ3VhbCBhIHNpZ21hLCBkb25kZSBuIGluZGljYSBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGEgZ2VuZXJhci4gDQoNCmBgYHtyfQ0KIyBWZWN0b3IgZGUgbXUgPSBtZWRpYXMNCm11ID0gYygwLCAzLCAtNSwgMi4xKQ0KIyBWZWN0b3IgZGUgc2lnbWEgPSBkZXN2w61vcyBlc3TDoW5kYXINCnNpZ21hID0gYygxLCAwLjUsIDUsIDAuMDEpDQojIEdlbmVyYW1vcyAxMCBvYnNlcnZhY2lvbmVzIGEgcGFydGlyIGRlIG51ZXN0cm9zIHZlY3RvcmVzDQptYXAyKC54ID0gbXUsIC55ID0gc2lnbWEsIC5mPSBybm9ybSwgbiA9IDEwKQ0KYGBgDQoNCioqRWplbXBsbyBjb24gc3RyaW5ncyoqDQoNCkFob3JhIHZlYW1vcyBjb24gdW4gZWplbXBsbyBwYXJhIGNyZWFyIHVuYSBsaXN0YSBkZSBjb21wcmFzLg0KDQpgYGB7cn0NCiMgVmVjdG9yZXMNCmtpbG9zID0gYygxMCwgMSwgNiwgOCwgMykNCnZlcmR1cmFzID0gIGMoInBhcGEiLCAiYmF0YXRhIiwgInphcGFsbG8iLCAiYm9uaWF0byIsICJ6YW5haG9yaWEiKQ0KIyBjb25jYXRlbmFtb3MgbGFzIHBhbGFicmFzIChjb24gbm9tYnJlcyBlbiBtYXnDunNjdWxhKSB5IHZhbG9yZXMNCm1hcDJfY2hyKC54ID0gdmVyZHVyYXMsIC55ID1raWxvcywgLmYgPSBmdW5jdGlvbih4LHkpIHN0cl9jKHN0cl90b190aXRsZSh4KSx5LCBzZXA9IjogIikpDQpgYGANCiAgDQogIA0KVXNhbW9zIGBwbWFwYCBjdWFuZG8gcXVlcmVtb3MgYXBsaWNhciB1bmEgZnVuY2nDs24gc29icmUgdW5hIGxpc3RhIGRlIGFyZ3VtZW50b3M6DQoNCi0gcG1hcCgubCwgLmYsIC4uLikNCi0gcG1hcChWRUNUT1JfT19MSVNUX0RFX0lOUFVUUywgRlVOQ1RJT05fQV9BUExJQ0FSLCBPVFJPU19PUENJT05BTEVTKQ0KDQpWb2x2YW1vcyBhbCBlamVtcGxvIHBhcmEgZ2VuZXJhciBvYnNlcnZhY2lvbmVzIGRlIGRpc3RyaWJ1Y2lvbmVzIG5vcm1hbGVzIGEgcGFydGlyIGRlIGxvcyB2ZWN0b3JlcyBkZSBtdSB5IHNpZ21hLCBwZXJvIGFob3JhIGluY29ycG9yYW1vcyBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGNvbW8gdW4gYXJndW1lbnRvIGFkaWNpb25hbCAoZXN0ZSBlamVtcGxvIHRhbWJpw6luIGVzIGRlbCBsaWJybyAqUiBmb3IgRGF0YSBTY2llbmNlKikuDQoNCmBgYHtyfQ0KIyBWZWN0b3IgZGUgbXUNCm11ID0gYygwLCAzLCAtNSwgMi4xKQ0KIyBWZWN0b3IgZGUgc2lnbWENCnNpZ21hID0gYygxLCAwLjUsIDUsIDAuMDEpDQojIFZlY3RvciBkZSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIA0KbiA9IGMoMTo0KQ0KIyBMaXN0YSBkZSBhcmd1bWVudG9zDQphcmd1bWVudG9zIDwtIGxpc3QobiwgbXUsIHNpZ21hKQ0KIyBHZW5lcmFtb3MgbGFzIG9ic2VydmFjaW9uZXMgYSBwYXJ0aXIgZGUgbnVlc3RyYSBsaXN0YSBkZSBwYXLDoW1ldHJvcw0KcG1hcCgubCA9IGFyZ3VtZW50b3MsIC5mID0gcm5vcm0pDQpgYGANCg0KIyMgUFVSUlI6IERhdGFmcmFtZXMgeSBtb2RlbG9zDQoNCiMjIyBEYXRhZnJhbWVzDQoNCkxhcyBmdW5jaW9uZXMgZGUgUFVSUlIgc2UgcHVlZGVuIHV0aWxpemFyIHBhcmEgcmVhbGl6YXIgb3BlcmFjaW9uZXMgc29icmUgZGF0YWZyYW1lcy4gTGEgc2ludGF4aXMgcXVlIHZhbW9zIGEgdXRpbGl6YXIgZXM6DQoNCmBtYXAoZGF0YWZyYW1lLCBmdW5jacOzbilgDQoNCkRlIGVzdGEgbWFuZXJhIHZhbW9zIGEgZXN0YXIgYXBsaWNhbmRvIGxhIGZ1bmNpw7NuIHNvYnJlIHRvZGFzIGxhcyBjb2x1bW5hcyBkZWwgZGF0YWZyYW1lLiBWZWFtb3MgdW4gZWplbXBsbyB1c2FuZG8gbnVlc3RyYSBlbmN1ZXN0YSBkZSBzdWVsZG9zIGRlbCBzZWN0b3IgSVQgbGltcGlhLiANCg0KYGBge3J9DQojIGNhcmdhbW9zIGVsIGRhdGFzZXQNCmVuY3Vlc3RhX3N1ZWxkb3NfbGltcGlhID0gcmVhZF9jc3YoIi4uL0Z1ZW50ZXMvZW5jdWVzdGFfZHNfZmluYWwuY3N2IikNCmVuY3Vlc3RhX3N1ZWxkb3NfbGltcGlhIDwtIGVuY3Vlc3RhX3N1ZWxkb3NfbGltcGlhICU+JQ0KICAjIGNyZWFtb3MgdmFyaWFibGUgcGVyZmlsIEJJL0RBIHZzIERTL0RJDQogIG11dGF0ZShwZXJmaWwgPSBjYXNlX3doZW4odHJhYmFqb19kZSA9PSAiQkkgQW5hbHlzdCAvIERhdGEgQW5hbHlzdCIgfiAiQkkvREEiLCBUUlVFIH4gIkRTL0RFIikpDQpgYGANCg0KQ29tZW5jZW1vcyBhbmFsaXphbmRvIGxhIGNhbnRpZGFkIGRlIHZhbG9yZXMgZGlzdGludG9zIHBhcmEgY2FkYSBjb2x1bW5hLiBQYXJhIGVsbG8gdXNhbW9zIGxhIGZ1bmNpw7NuIGBuX2Rpc3RpbmN0YC4NCg0KYGBge3J9DQptYXAoZW5jdWVzdGFfc3VlbGRvc19saW1waWEsIG5fZGlzdGluY3QpDQpgYGANCg0KVXNhbmRvIGxhIGZ1bmNpw7NuIGBzdHIoKWAgc2UgcHVlZGUgY29uc3VsdGFyIGRlIGZvcm1hIGNvbXBhY3RhIGxhIGVzdHJ1Y3R1cmEgaW50ZXJuYSBkZSB1biBvYmpldG8gUi4gRXMgZXNwZWNpYWxtZW50ZSBhZGVjdWFkbyBwYXJhIG1vc3RyYXIgZGUgZm9ybWEgY29tcGFjdGEgZWwgY29udGVuaWRvIChhYnJldmlhZG8pIGRlIGxpc3Rhcy4gDQoNCmBgYHtyfQ0KbWFwKGVuY3Vlc3RhX3N1ZWxkb3NfbGltcGlhLCBuX2Rpc3RpbmN0KSAlPiUNCiAgc3RyKCkgIyBBZ3JlZ2Ftb3Mgc3RyIHBhcmEgcXVlIGxhIHNhbGlkYSBsdXpjYSBtZWpvcg0KYGBgDQoNCkFob3JhIGNhbGN1bGFtb3MgZWwgcHJvbWVkaW8gc29icmUgbGFzIGNvbHVtbmFzIG51bcOpcmljYXMuDQoNCmBgYHtyfQ0KZW5jdWVzdGFfc3VlbGRvc19saW1waWEgJT4lIA0KICBzZWxlY3RfaWYoaXMubnVtZXJpYykgJT4lIA0KICBtYXAobWVhbikgJT4lIA0KICBzdHIoKQ0KYGBgDQoNCkNhbGN1bGFtb3MgbGEgbWVkaWFuYSBzb2JyZSBsYXMgY29sdW1uYXMgbnVtw6lyaWNhcy4gU2FiaWVuZG8gcXVlIGVsIHJlc3VsdGFkbyBkZSBsYSBmdW5jacOzbiBlcyB1biBuw7ptZXJvIHBvZGVtb3MgdXNhciBkaXJlY3RhbWVudGUgbGEgZnVuY2nDs24gYG1hcF9kYmxgLg0KDQpgYGB7cn0NCiMgdXNhbmRvIG1hcF9kYmwgDQplbmN1ZXN0YV9zdWVsZG9zX2xpbXBpYSAlPiUgDQogIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUgDQogIG1hcF9kYmwobWVkaWFuKQ0KYGBgDQoNClJlY29yZGVtb3MgcXVlIGVzdGEgZnVuY2nDs24gZGV2dWVsdmUgdW4gdmVjdG9yIG51bcOpcmljbyBubyB1bmEgbGlzdGEgY29tbyBlbiBlbCBjYXNvIGFudGVyaW9yLCBwb3IgZXNvIG5vIHVzYW1vcyBsYSBmdW5jacOzbiBgc3RyKClgIHBhcmEgdmVyIGVsIHJlc3VsdGFkbyBlbiBmb3JtYSBjb21wYWN0YS4NCg0KIyMjIE1vZGVsb3MNCg0KUG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIG1hcCBwYXJhIGNhbGN1bGFyIGRpc3RpbnRvcyBtb2RlbG9zIG1vZGVsb3MuIFBvciBlamVtcGxvLCBjYWxjdWxlbW9zIHVuIG1vZGVsbyBkaXN0aW50byBzZWfDum4gZWwgcGVyZmlsIChEQSB5IERTKS4NCg0KYGBge3J9DQptb2RlbG9zX3Bvcl9wZXJmaWwgPSBlbmN1ZXN0YV9zdWVsZG9zX2xpbXBpYSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAjIHNlcGFyYW1vcyBlbCBkYXRhc2V0IHNlZ8O6biBwZXJmaWwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0KC4kcGVyZmlsKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAjIGNyZWFtb3MgdW4gbW9kZWxvIHBhcmEgY2FkYSBwZXJmaWwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgbWFwKH5sbShzYWxhcmlvX25ldG8gfiBlZGFkLCBkYXRhID0gLikpDQpgYGANCg0KRWwgcmVzdWx0YWRvIGRlIGVzdG8gZXMgdW5hIGxpc3RhIGNvbiBsb3MgZG9zIG1vZGVsb3MuIEFuYWxpY2Vtb3MgbG9zIGVsZW1lbnRvcyBkZSBjYWRhIHVubyB1dGlsaXphbmRvIGxhIGZ1bmNpw7NuIHRpZHkoKSBkZSBsYSBsaWJyZXLDrWEgYnJvb20uIA0KDQpgYGB7cn0NCm1vZGVsb3NfcG9yX3BlcmZpbCAlPiUgDQogIG1hcChicm9vbTo6dGlkeSkNCmBgYA0KRXZhbHVlbW9zIGFob3JhIGxhIGNhcGFjaWRhZCBleHBsaWNhdGl2YSBkZSBsb3MgbW9kZWxvcy4gUGFyYSBlbGxvLCBjb21wYXJhbW9zIGVsICRSXjIkIGRlIGNhZGEgdW5vIChlbiBlc3RlIGNhc28gY29tbyBzb24gbW9kZWxvcyBzaW1wbGVzIHBvZGVtb3MgdXRpbGl6YXIgZXN0YSBtw6l0cmljYSkuIA0KDQpgYGB7cn0NCm1vZGVsb3NfcG9yX3BlcmZpbCAlPiUgDQogIG1hcChicm9vbTo6Z2xhbmNlKSAlPiUgDQogIG1hcF9kYmwoInIuc3F1YXJlZCIpDQpgYGANCg0KTWllbnRyYXMgZWwgbW9kZWxvIGRlIGVkYWQgcGFyYSBsb3MgcGVyZmlsZXMgQkkvREEgbG9ncmEgZXhwbGljYXIgZWwgMTUuNiUgZGUgbGEgdmFyaWFiaWxpZGFkIGRlbCBmZW7Ds21lbm8sIGVsIG1vZGVsbyBkZSBlZGFkIHBhcmEgbG9zIERTL0RFIGxvZ3JhIGNhcHRhciB1biBwb3JjZW50YWplIG1lbm9yIGRlIGxhIHZhcmlhYmlsaWRhZCAoMTIsOSUpLiANCg0KIyMgRWplbXBsbzogSXRlcmFuZG8gZW4gbGEgRVBIDQoNCkxvIHByaW1lcm8gcXVlIG5lY2VzaXRhbW9zIGVzIGRlZmluaXIgdW4gdmVjdG9yIG8gbGlzdGEgc29icmUgZWwgcXVlIGl0ZXJhci4gDQoNClBvciBlamVtcGxvLCBwb2RlbW9zIGFybWFyIHVuIHZlY3RvciBjb24gbG9zIHBhdGggYSBsYXMgYmFzZXMgaW5kaXZpZHVhbGVzLCBjb24gZWwgY29tYW5kbyBgZnM6OmRpcl9sc2AuIFNlIGxlIGVzcGVjaWZpY2EgZWwgX19wYXRoX18gZG9uZGUgYnVzY2FyIGxvcyBhcmNoaXZvcyB5IGVuIGVzdGUgY2FzbyB1bmEgZXhwcmVzaW9uIHJlZ3VsYXIgX19yZWdleHBfXyBwYXJhIHF1ZSBkZXZ1ZWx2YSBhcXVlbGxvcyBhcmNoaXZvcyBxdWUgY29pbmNpZGVuIGNvbiBsYSBtaXNtYS4gDQoNCmBgYHtyfQ0KIyBCdXNjYW1vcyBlbiBlbCBwYXRoIGFxdWVsbG9zIGFxdWVsbG9zIGFyY2hpdm9zIHF1ZSBtYXRjaGVhbiBhIGxhIGV4cHJlc2lvbiByZWd1bGFyDQpiYXNlc19pbmRpdmlkdWFsZXNfcGF0aCA8LSBkaXJfbHMocGF0aCA9ICcuLi9GdWVudGVzLycsIHJlZ2V4cCA9ICd1c3VfaW5kaXZpZHVhbF9UJykNCmJhc2VzX2luZGl2aWR1YWxlc19wYXRoDQpgYGANCg0KTHVlZ28sIGNvbW8gZW4gbGEgZnVuY2nDs24gcXVlIHVzYW1vcyBwYXJhIGxlZXIgbGFzIGJhc2VzIGRlZmluaW1vcyBtdWNob3MgcGFyw6FtZXRyb3MsIG5vcyBwb2RlbW9zIGFybWFyIHVuYSBmdW5jacOzbiBfd3JhcHBlcl8gcXVlIHPDs2xvIG5lY2VzaXRlIHVuIHBhcsOhbWV0cm8geSBzaW1wbGlmaXF1ZSBsYSBlc2NyaXR1cmEgZGVsIG1hcC4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgTGVlciBsYSBiYXNlIGRlIEVQSCB0b21hbmRvIGNvbW8gYXJndW1lbnRvIGVsIGZpbGVfcGF0aA0KbGVlcl9iYXNlX2VwaCA8LSBmdW5jdGlvbihwYXRoKSB7DQogICMgTGVjdHVyYSBkZSBhcmNoaXZvcw0KICByZWFkLnRhYmxlKHBhdGgsIHNlcCA9ICI7IiwgZGVjID0gIiwiLCBoZWFkZXIgPSBUUlVFLCBmaWxsID0gVFJVRSkgJT4lDQogICMgU2VsZWNjaW9uYW1vcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMNCiAgc2VsZWN0KEFOTzQsIFRSSU1FU1RSRSwgUkVHSU9OLCBQMjEsIENIMDQsIENIMDYpDQp9DQpgYGANCg0KQ3JlYW1vcyB1bmEgdGFibGEgcXVlIGluY2x1eWEgbG9zIHBhdGhzIGRlIGNhZGEgYmFzZSBlbiB1bmEgY29sdW1uYSB5IGxvcyBkYXRhZnJhbWVzIGFuaWRhZG9zIGNvcnJlc3BvbmRpZW50ZXMgYSBjYWRhIGJhc2UgZW4gb3RyYS4gDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGNyZWFtb3MgbGEgdGFibGEgZGUgYmFzZXMNCmJhc2VzX2RmIDwtIHRpYmJsZShiYXNlc19pbmRpdmlkdWFsZXNfcGF0aCkgJT4lDQogIG11dGF0ZShiYXNlID0gbWFwKC54ID0gYmFzZXNfaW5kaXZpZHVhbGVzX3BhdGgsIC5mID0gbGVlcl9iYXNlX2VwaCkpDQpiYXNlc19kZg0KYGBgDQoNCkVsIHJlc3VsdGFkbyBlcyB1biBkYXRhZnJhbWUgZG9uZGUgbGEgY29sdW1uYSBfX2Jhc2VfXyB0aWVuZSBlbiBjYWRhIGZpbGEgZWwgZGF0YWZyYW1lIGNvbiBsYSBiYXNlIGRlIGxhIEVQSCBkZSBlc2UgcGVyw61vZG8uIEVzdG8gZXMgbG8gcXVlIGxsYW1hbW9zIHVuIF9uZXN0ZWQgREZfIG8gZGF0YWZyYW1lIGFuaWRhZG8uDQoNClNpIHF1ZXJlbW9zIGFicmlyICgiZGVzYW5pZGFyIikgZXN0b3MgZGF0YWZyYW1lcyBhbmlkYWRvcyB1c2Ftb3MgZWwgY29tYW5kbyBgdW5uZXN0KClgLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBEZXNhbmlkYW1vcyBlbCBkYXRhZnJhbWUNCmJhc2VzX2RmIDwtIGJhc2VzX2RmICU+JSB1bm5lc3QoKQ0KYmFzZXNfZGYNCmBgYA0Kwr9RdcOpIHBhc2Egc2kgbG9zIGRhdGFmcmFtZXMgcXVlIHRlbmVtb3MgYW5pZGFkb3Mgbm8gdGllbmVuIGxhIG1pc21hIGNhbnRpZGFkIGRlIGNvbHVtbmFzPw0KDQpFc3RvIG1pc21vIGxvIHBvZGVtb3MgdXNhciBwYXJhIGZyYWdtZW50YXIgZWwgZGF0YXNldCBwb3IgYWxndW5hIHZhcmlhYmxlLCBjb24gbGEgZnVuY2lvbiBgZ3JvdXBfYnkoKWANCg0KYGBge3J9DQpiYXNlc19kZiAlPiUgDQogICMgQWdydXBhciBwb3IgZ8OpbmVybw0KICBncm91cF9ieShDSDA0KSAlPiUgDQogICMgQW5pZGFyDQogIG5lc3QoKQ0KYGBgDQo+IMK/IERlIHF1w6kgc2lydmUgdG9kbyBlc3RvPw0KDQpObyB0b2RvIGVuIGxhIHZpZGEgZXMgdW4gRGF0YWZyYW1lLiBIYXkgZXN0dWN0dXJhcyBkZSBkYXRvcyBxdWUgbm8gc2UgcHVlZGVuIG5vcm1hbGl6YXIgYSBmaWxhcyB5IGNvbHVtbmFzLiBFbiBlc29zIGNhc29zIHJlY3VycsOtYW1vcyB0cmFkaWNpb25hbG1lbnRlIGEgbG9zIGxvb3BzLiBDb24gTUFQIHBvZGVtb3MgdGVuZXIgbG9zIGVsZW1lbnRvcyBhZ3J1cGFkb3MgZW4gdW4gc8OzbG8gb2JqZXRvIHkgYcO6biBjb25zZXJ2YXIgc3VzIGZvcm1hcyBkaWZlcmVudGVzLg0KDQojIyBFamVtcGxvLiBSZWdyZXNpw7NuIGxpbmVhbCBzaW1wbGUNCg0KUGxhbnRlYW1vcyB1biBtb2RlbG8gcGFyYSBleHBsaWNhciBlbCBpbmdyZXNvIChQMjEgZW4gbnVlc3RyYSBiYXNlKSBlbiBmdW5jacOzbiBkZSBsYSBlZGFkIChDSDA2IGVuIGxhIGJhc2UpLiANCg0KJCQNCkluZ3Jlc28gPSBcYmV0YV8wICsgXGJldGFfMSpFZGFkDQokJA0KQ2FsY3VsYW1vcyB1bmEgcmVncmVzaW9uIGxpbmVhbCBzb2JyZSBlbCBkYXRhc2V0IHF1ZSBjb250aWVuZSB0b2RhcyBsYXMgYmFzZXMgZGUgRVBILiANCg0KYGBge3J9DQpsbWZpdCA8LSBsbShQMjF+Q0gwNiwgZGF0YSA9IGJhc2VzX2RmKQ0KIyBSZXN1bWVuIGRlbCBtb2RlbG8NCmJyb29tOjp0aWR5KGxtZml0KQ0KYGBgDQoNCihhbCBmaW5hbCBkZSBsYSBjbGFzZSBwb2RlbW9zIGNoYXJsYXIgc29icmUgbG9zIHJlc3VsdGFkb3MpDQoNCkRlIGZvcm1hIFRpZHksIGxhIGxpYnJlcsOtYSBgYnJvb21gIG5vcyBkYSBsb3MgcmVzdWx0YWRvcyBzb2JyZSBsb3MgY29lZmljaWVudGVzIGVuIHVuIERGLg0KDQpgYGB7cn0NCmJyb29tOjp0aWR5KGxtZml0KQ0KYGBgDQogDQpTaSBsbyBxdWVyZW1vcyBoYWNlciBwb3IgZ8OpbmVybywgdmVhbW9zIGPDs21vIHBvZGVtb3MgaGFjZXJsbyB1c2FuZG8gTUFQLiANCiANCiMjIFVzYW5kbyBNQVANCg0KUHJpbWVybyBhcm1hbW9zIHVuYSBmdW5jaW9uIHF1ZSBzaW1wbGlmaWNhIGVsIGPDs2RpZ28geSBub3MgZGV2dWVsdmUgbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMgZGVsIG1vZGVsbyBsaW5lYWwgY2FsY3VsYWRvIHNvYnJlIGVsIGRhdGFmcmFtZSBfX2Jhc2VfXy4gDQoNCmBgYHtyfQ0KZnVuIDwtIGZ1bmN0aW9uKGJhc2UsIGdydXBvKXticm9vbTo6dGlkeShsbShQMjF+Q0gwNiwgZGF0YSA9IGJhc2UpKX0NCmBgYA0KDQpDYWxjdWxhbW9zIGxhIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgY2FkYSBncnVwby4gRW4gZXN0ZSBjYXNvIGVsZWdpbW9zIGfDqW5lcm8sIHBlcm8gcG9kcsOtYSBoYWJlciBzaWRvIHBvciByZWdpw7NuIHRhbWJpw6luIHUgb3RyYSB2YXJpYWJsZSBkZSBpbnRlcsOpcy4gDQoNCmBgYHtyfQ0KYmFzZXNfZGZfbG0gPC0gYmFzZXNfZGYgJT4lIA0KICAjIEFncnVwYW1vcyBwb3IgZ8OpbmVybw0KICBncm91cF9ieShDSDA0KSAlPiUNCiAgIyBBbmlkYW1vcw0KICBuZXN0KCkgJT4lIA0KICAjIENyZWFtb3MgdW5hIHZhcmlhYmxlIHF1ZSBjb250ZW5nYSBlbCBkYXRhZnJhbWUgcmVzdWx0YW50ZSBkZSBhcGxpY2FyIGxhIGZ1bmNpw7NuIHF1ZSBjcmVhbW9zIGFudGVzDQogIG11dGF0ZShsbSA9IG1hcChkYXRhLCBmdW4pKQ0KYmFzZXNfZGZfbG0NCmBgYA0KT2J0ZW5lbW9zIGNvbW8gcmVzdWx0YWRvIHVuIGRhdGEgZnJhbWUgY29uIGxvcyBkb3MgbW9kZWxvcyBhbmlkYWRvcy4gVmVhbW9zIGxvcyByZXN1bHRhZG9zIGRlbCBtb2RlbG8sIGRlc2FuaWRhbmRvIGxhIHZhcmlhYmxlIGxtLiANCg0KYGBge3J9DQojIERlc2FuaWRhbW9zIHBvciBsYSB2YXJpYWJsZSBsbQ0KYmFzZXNfZGZfbG0gJT4lIA0KICB1bm5lc3QobG0pDQpgYGANCg0KIyMjIyBtZSBxdWVkw6kgYWPDoSAtIGZhbHRhIGFjdHVhbGl6YXIgV0FMSyAjIyMjIA0KDQojIyBXYWxrDQoNCkxhcyBmdW5jaW9uZXMgYFdhbGtgIHRpZW5lbiBsYSBtaXNtYSBmb3JtYSBxdWUgbG9zIGBtYXBgLCBwZXJvIHNlIHVzYW4gY3VhbmRvIGxvIHF1ZSBxdWVyZW1vcyBpdGVyYXIgbm8gZ2VuZXJhIHVuYSBzYWxpZGEsIHNpbm8gcXVlIG5vcyBpbnRlcmVzYW4gbG9zIGVmZWN0b3Mgc2VjdW5kYXJpb3MgcXVlIGdlbmVyYW4uDQoNCmBgYHtyfQ0KbWFwMigueCA9IEFCQ18xMjMkTGV0cmFzLC55ID0gQUJDXzEyMyROdW0sLmYgPSBmdW5jaW9uX3BydWViYSlbMTozXQ0KYGBgDQoNCmBgYHtyfQ0Kd2FsazIoLnggPSBBQkNfMTIzJExldHJhcywueSA9IEFCQ18xMjMkTnVtLC5mID0gZnVuY2lvbl9wcnVlYmEpDQpgYGANCg0KTm90ZW1vcyBxdWUgYHdhbGsyYCBubyBkZXZvbHZpw7MgdW4gcmVzdWx0YWRvDQoNCmBgYHtyfQ0KIyBmdW5jaW9uIHBhcmEgaW1wcmltaXINCmltcHJpbWlyX3NhbGlkYSA8LSBmdW5jdGlvbih4LHkpew0KICBwcmludChmdW5jaW9uX3BydWViYSh4LHkpKQ0KfQ0KDQojIE1hcA0KbWFwMihBQkNfMTIzJExldHJhcyxBQkNfMTIzJE51bSxpbXByaW1pcl9zYWxpZGEpDQpgYGANCg0KYGBge3J9DQojIFdhbGsNCndhbGsyKEFCQ18xMjMkTGV0cmFzLEFCQ18xMjMkTnVtLGltcHJpbWlyX3NhbGlkYSkNCg0KYGBgDQoNCkVzbyBxdWUgdmVtb3MgZXMgZWwgZWZlY3RvIHNlY3VuZGFyaW8gZGVudHJvIGRlIGxhIGZ1bmNpw7NuIChpbXByaW1pcikNCg0KIyMgRGlzY3VzacOzbi4NCg0KPiDCv0N1w6FuZG8gdXNhciBlc3RhcyBoZXJyYW1pZW50YXM/DQoNCkVuIGVsIGN1cnNvIGhlbW9zIHZpc3RvIGRpZmVyZW50ZXMgdMOpY25pY2FzIHBhcmEgbWFuaXB1bGFjacOzbiBkZSBkYXRvcy4gRW4gcGFydGljdWxhciwgbGEgbGlicmVyw61hIGRwbHlyIG5vcyBwZXJtaXTDrWEgZsOhY2lsbWVudGUgbW9kaWZpY2FyIHkgY3JlYXIgbnVldmFzIHZhcmlhYmxlcywgYWdydXBhbmRvLiDCv0N1YW5kbyB1c2Ftb3MgYGRwbHlyYCB5IGN1YW5kbyB1c2Ftb3MgYHB1cnJyYD8NCg0KLSBTaSB0cmFiYWphbW9zIHNvYnJlIHVuIERGIHNpbXBsZSwgc2luIHZhcmlhYmxlcyBhbmlkYWRhcyAobG8gcXVlIGNvbm9jw61hbW9zIGhhc3RhIGhveSkgcG9kZW1vcyB1c2FyIGBkcGx5cmANCg0KLSBTaSBxdWVyZW1vcyB0cmFiYWphciBjb24gREYgYW5pZGFkb3MsIGNvbiBjb3NhcyBxdWUgbm8gc29uIERGLCBvIHNpIGVsIHJlc3VsdGFkbyBkZSBsYSBvcGVyYWNpw7NuIHF1ZSB2YW1vcyBhIHJlYWxpemFyIGEgbml2ZWwgYXJjaGl2byBlcyBhbGdvIGRpc3RpbnRvIGEgdW4gdmFsb3Igw7puaWNvLCBub3MgY29udmllbmUgdXNhciBgbWFwYCB5IGBwdXJycmAuDQoNCi0gTGFzIGZ1bmNpb25lcyBgd2Fsa2Agc29uIMO6dGlsZXMgcG9yIGVqZW1wbG8gcGFyYSBlc2NyaWJpciBhcmNoaXZvcyBlbiBkaXNjbyBkZSBmb3JtYSBpdGVyYXRpdmEuIEFsZ28gcXVlIG5vIGdlbmVyYSB1bmEgc2FsaWRhLiANCg0K